/*
 * Copyright 2007 - The JDPF Project Team (http://www.jdpf.org) 
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at 
 * 
 * http://www.apache.org/licenses/LICENSE-2.0 
 * 
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and 
 * limitations under the License. 
 */
package org.jdpf.core.plugins.parameters;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.List;
import java.util.StringTokenizer;

import org.jdpf.core.kernel.exceptions.CoreException;
import org.jdpf.core.kernel.exceptions.CoreExceptionsEnum;
import org.jdpf.core.kernel.exceptions.IllegalParametersException;
import org.jdpf.core.kernel.plugins.BundleToolBox;

/**
 * 
 * @author Paolo Ciccarese
 * @author Cristiana Larizza
 * 
 */
public class PluginParameterValidator extends AbstractParametersValidator {

	private String transitionId;

	public PluginParameterValidator(String pluginId, String id, boolean logging) {
		super(logging, pluginId);
		this.transitionId = id;
	}

	/**
	 * Checks the parameter of a specific instance of the plugin against its
	 * manifest.
	 * 
	 * @param param
	 *            The list of parameters
	 * @throws CoreException
	 * @throws IOException
	 */
	public void checkParameter(Parameters param) throws CoreException {

		if (logging)
			logger.logMainPoint(this, pluginId, transitionId,
					"Parameters values validation started.");

		Dictionary dictionary = BundleToolBox.getDefault().getHeaders(pluginId);

		Enumeration manifestKeys = dictionary.keys();
		while (manifestKeys.hasMoreElements()) {
			String key = (String) manifestKeys.nextElement();
			if (key.startsWith("Param-")) {
				String paramName = key.substring(6);
				String paramManifestValue = (String) dictionary.get(key);

				String paramValue = null;
				try {
					paramValue = (String) param.getParameterValue(transitionId,
							paramName);
					if (logging)
						logger.log(this, pluginId, transitionId,
								"Check for paramName: " + paramName
										+ " loaded value: " + paramValue);
				} catch (Exception e) {
					logger.error(this, pluginId, transitionId,
							"Getting parameter '" + paramName
									+ "' value of block " + pluginId
									+ " failed: " + e.getMessage());
				}

				char left, right;
				String[] tokens = paramManifestValue.split(";");
				cleanSpaces(tokens);
				if (tokens.length > 0) {
					StringTokenizer st = new StringTokenizer(tokens[0]);
					String paramType = st.nextToken();
					// Checks if the type of the parameter is allowed
					ParametersTypesEnum parameterType = parameterTypeValidation(paramType);

					boolean isMandatory = true; // Default value: Mandatory
					if (st.countTokens() > 0) {
						isMandatory = isParameterMandatory(st.nextToken());
					}

					if (isMandatory && paramValue == null) {
						throw new IllegalParametersException("The parameter '"
								+ paramName + "' is mandatory.", transitionId, paramName);
					}

					if (paramValue != null) {
						try {
							parameterValueValidation(parameterType, paramValue);
						} catch (Exception e) {
							throw new IllegalParametersException("The value '"
									+ paramValue + "' for the parameter '"
									+ paramName + "' is not a valid value.", transitionId,
									paramName);
						}

						cleanSpaces(tokens);
						if (tokens.length > 1) {
							left = getLeft(tokens[1]);
							right = getRight(tokens[1]);
							if (left == '{' && right == '}') {
								List<String> allowedValues = parameterAllowedValuesValidation(
										paramName, parameterType, tokens[1]
												.substring(1, tokens[1]
														.length() - 1));
								if (!parameterType.isBoolean())
									parameterValueInSetValidation(paramName,
											paramType, paramValue,
											allowedValues);
							} else if ((left == '[' || left == ']')
									&& (right == ']' || right == '[')) {
								parameterAllowedRangeValidation(parameterType,
										paramValue, paramName, tokens[1]
												.substring(1, tokens[1]
														.length() - 1), left,
										right);
							} else {
								logger
										.warning(
												this,
												pluginId,
												transitionId,
												"The restriction '"
														+ tokens[1]
														+ "' has not been recognized and will be ignored!");
							}
						}
					}
				} else {
					throw new IllegalParametersException(
							"The parameter value has to include at least the parameter type: "
									+ "{string,int,float,double, boolean}",
							transitionId, paramName);
				}
			}
		}
		if (logging)
			logger.logMainPoint(this, pluginId, transitionId,
					"Parameters values validation completed.");
	}

	private void parameterAllowedRangeValidation(
			ParametersTypesEnum parameterType, String value, String name,
			String values, char left, char right) throws CoreException {
		if (logging)
			logger.log(this, pluginId, transitionId, value + " Range: "
					+ values);
		String[] tokens = values.split(",");
		double a, b;
		try {
			switch (tokens.length) {
			case 1:
				a = Double.parseDouble(tokens[0]);
				if (Double.parseDouble(value) < a
						|| (Double.parseDouble(value) == a && right == ']')) {
					throw new IllegalParametersException("The parameter '"
							+ name + "' has value '" + value
							+ "' that is not in the range: " + left + values
							+ right, transitionId, name);
				}
				break;
			case 2:
				if (tokens[0].length() > 0) {
					a = Double.parseDouble(tokens[0]);
					b = Double.parseDouble(tokens[1]);
					if ((Double.parseDouble(value) > a || (Double
							.parseDouble(value) == a && left == '['))
							&& (Double.parseDouble(value) < b)
							|| (Double.parseDouble(value) == b && right == ']'))
						break;
				} else {
					b = Double.parseDouble(tokens[1]);
					if (Double.parseDouble(value) < b
							|| (Double.parseDouble(value) == b && right == ']'))
						break;
				}
			default:
				throw new IllegalParametersException(
						"The parameter '" + name + "' has value '" + value
								+ "' that is not in the range: " + left + values
								+ right, transitionId, name);
			}

		} catch (NumberFormatException e) {
			throw new IllegalParametersException("The parameter allowed range "
					+ values
					+ " should only include numbers. A range has to be "
					+ "defined in the format [a, b] with a<b or [a,] or [,b]",
					transitionId, name);
		}
	}

	private List<String> parameterAllowedValuesValidation(String parameterName,
			ParametersTypesEnum parameterType, String values)
			throws CoreException {
		List<String> allowedValues = new ArrayList<String>();
		StringTokenizer st1 = new StringTokenizer(values, ",");
		if (parameterType.isNumeric() || parameterType.isString()) {
			while (st1.hasMoreTokens()) {
				String paramAllowedValue = st1.nextToken();
				try {
					parameterTypeValueValidation(parameterType,
							paramAllowedValue);
					// If the type is string we simply add it to the list
					allowedValues.add(paramAllowedValue);
				} catch (Exception e) {
					throw new IllegalParametersException(
							"The parameter allowed value " + paramAllowedValue
									+ " is not valid. Allowed "
									+ "values has to be: "
									+ parameterType.getStringValue(),
							transitionId, parameterName);
				}
			}
		} else if (parameterType.isBoolean()) {
			logger
					.warning(
							this,
							pluginId,
							transitionId,
							"The allowed boolean values are {true, false}. Any value restriction will be ignored!");
			allowedValues.add("true");
			allowedValues.add("false");
		}
		return allowedValues;
	}

	private void parameterValueValidation(ParametersTypesEnum parameterType,
			String value) throws CoreException, URISyntaxException {
		parameterTypeValueValidation(parameterType, value);
	}

	private void parameterValueInSetValidation(String paramName,
			String paramType, String value, List<String> allowedValues)
			throws CoreException {
		if (logging)
			logger.log(this, pluginId, transitionId, "Is in set: " + value
					+ " " + allowedValues);

		boolean flag = false;
		for (String allowedValue : allowedValues) {
			if (allowedValue.equalsIgnoreCase(value))
				flag = true;
		}

		if (!flag)
			throw new IllegalParametersException("The parameter '" + paramName
					+ "' has value '" + value
					+ "' that is not in the list of allowed values: "
					+ allowedValues, transitionId, paramName);
	}

	private void parameterTypeValueValidation(
			ParametersTypesEnum parameterType, String value)
			throws URISyntaxException {
		if (parameterType.isInt()) {
			Integer.parseInt((String) value);
		} else if (parameterType.isFloat()) {
			Float.parseFloat((String) value);
		} else if (parameterType.isDouble()) {
			Double.parseDouble((String) value);
		} else if (parameterType.isURI()) {
			new URI(value.replace(" ", "%20"));
		}
	}

	private void cleanSpaces(String s[]) {
		for (int i = 0; i < s.length; i++)
			s[i] = s[i].trim();
	}

	private char getLeft(String s) {
		return s.trim().charAt(0);
	}

	private char getRight(String s) {
		return s.trim().charAt(s.trim().length() - 1);
	}

}
